package com.rizzo.back.helper;

import com.rizzo.back.domain.Synonym;
import com.rizzo.back.util.search.SearchDataObject;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.Token;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.index.Term;
import org.apache.lucene.queryParser.ParseException;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.TermQuery;
import org.hibernate.LockMode;
import org.hibernate.search.FullTextSession;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;

import java.io.*;
import java.util.*;

/**
 * TODO Insert JavaDoc!
 */
public class SynonymHelper {

    private String hibernateSearchBaseDir;

    /**
     * TODO Method expandQuery ...
     *
     * @param query of type String
     * @param session of type FullTextSession
     * @param a of type Analyzer
     * @param field of type String
     * @param boost of type float
     * @return Query
     * @throws IOException when
     */
    public Query expandQuery(String query,
	                         FullTextSession session,
	                         Analyzer a,
	                         String field,
	                         float boost) throws IOException {
		Set<String> synsList = getSynonyms(query, session, a);
		BooleanQuery bq = new BooleanQuery();
		for (String synonym : synsList) {  // add in unique synonyms
			TermQuery tq = new TermQuery(new Term(field, synonym));
			tq.setBoost(boost);
			bq.add(tq, BooleanClause.Occur.SHOULD);
		}
		return bq;
	}

    public Query expandQuery(SearchDataObject searchDataObject, FullTextSession fullTextSession, Analyzer analyzer, float synonymBoost) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException, ParseException {
        Set<String> synonyms = getSynonyms(searchDataObject.getQueryString(), fullTextSession, analyzer);
        BooleanQuery booleanQuery = new BooleanQuery();
        booleanQuery.add(searchDataObject.getCombinedQuery(), BooleanClause.Occur.SHOULD);
		for (String synonym : synonyms) {
            String[] fields = searchDataObject.getFields();
            for (String field : fields) {
                TermQuery termQuery = new TermQuery(new Term(field, synonym));
                termQuery.setBoost(synonymBoost);
                booleanQuery.add(termQuery, BooleanClause.Occur.SHOULD);
            }
        }
        return booleanQuery;
    }

    /**
     * TODO Method getSynonyms ...
     *
     * @param query of type String
     * @param session of type FullTextSession
     * @param a of type Analyzer
     * @return Set<String>
     * @throws IOException when
     */
    public Set<String> getSynonyms(String query,
	                               FullTextSession session,
	                               Analyzer a) throws IOException {

		Set<String> querySet = new HashSet(); // avoid dups
		TokenStream ts = a.tokenStream("word", new StringReader(query));
		Token t = new Token();
		String anaQuery;
		while ((t = ts.next( t )) != null) {
			anaQuery = new String(t.termBuffer(), 0, t.termLength());
			querySet.add(anaQuery);
		}

		BooleanQuery bq = new BooleanQuery();
		for (String str : querySet) {
			TermQuery tq = new TermQuery(new Term("word", str));
			bq.add(tq, BooleanClause.Occur.SHOULD);
		}
		org.hibernate.search.FullTextQuery hibQuery = session.createFullTextQuery(bq, Synonym.class);
		hibQuery.setProjection("syn");

		List<Object[]> results = hibQuery.list();
		Set<String> alreadyPresent = new HashSet();

		for (Object[] obj : results) {
			StringTokenizer st = new StringTokenizer((String) obj[0], " ");
			while (st.hasMoreElements()) {
				String syn = (String) st.nextElement();
				alreadyPresent.add(syn);
			}
		}
		//alreadyPresent.addAll(querySet);   // add original query terms
		return alreadyPresent;
	}

    /**
     * TODO Method buildSynonymIndex ...
     *
     * @param session of type FullTextSession
     * @param synFile of type String
     * @throws IOException when
     */
    public void buildSynonymIndex(FullTextSession session, String synFile) throws IOException {
        //if index already there, don't do anything
        final File indexDirectory = new File(this.hibernateSearchBaseDir + System.getProperty("file.separator"), Synonym.class.getName());
        if (indexDirectory.exists() && indexDirectory.listFiles().length > 2) {
            return;
        }
        //prolog file must be there
        Resource res = new ClassPathResource(synFile);
        File file = res.getFile();
        if (!(file.exists())) {
            throw new IllegalStateException("WordNet Prolog file not found!");
        }
        if (!file.canRead()) {
            throw new IOException("Prolog file is not readable: " + file);
        }
        final FileInputStream fis = new FileInputStream(file);
        final BufferedReader br = new BufferedReader(new InputStreamReader(fis));
        String lineIn;
        final Map<String, List<String>> word2Groups = new TreeMap<String, List<String>>();// maps a word to all the "groups" it's in
        final Map<String, List<String>> group2Words = new TreeMap<String, List<String>>();// maps a group to all the words in it
        while ((lineIn = br.readLine()) != null) {
            if (!lineIn.startsWith("s(")) {                          // syntax check
                throw new IOException("Wrong input format " + lineIn);
            }
            lineIn = lineIn.substring(2);                            // parse line
            String id = lineIn.substring(0, lineIn.indexOf(','));
            int quote1 = lineIn.indexOf('\'');
            int quote2 = lineIn.lastIndexOf('\'');
            String word = lineIn.substring(quote1 + 1, quote2).toLowerCase();
            List<String> list = word2Groups.get(word);
            if (list == null) {
                list = new LinkedList<String>();
                list.add(id);
                word2Groups.put(word, list);
            } else {
                list.add(id);
            }
            list = group2Words.get(id);
            if (list == null) {
                list = new LinkedList<String>();
                list.add(word);
                group2Words.put(id, list);
            } else {
                list.add(word);
            }
        }
        fis.close();
        br.close();
        // create the index
        index(word2Groups, group2Words, session);
    }

    /**
     * TODO Method index ...
     *
     * @param word2Groups of type Map<String, List<String>>
     * @param group2Words of type Map<String, List<String>>
     * @param session of type FullTextSession
     * @throws IOException when
     */
    private void index(Map<String, List<String>> word2Groups,
                       Map<String, List<String>> group2Words, FullTextSession session)
            throws IOException {
        try {
            Iterator iter = word2Groups.keySet().iterator();
            int counter = 0;
            while (iter.hasNext()) { // for each word
                counter++;
                String word2GroupsKey = (String) iter.next();
                Synonym syn = new Synonym();
                syn.setId(counter);

                int n = index(word2Groups, group2Words, word2GroupsKey, syn);
                if (n > 0) {
                    syn.setWord(word2GroupsKey);
                    session.lock(syn, LockMode.NONE);
                    session.index(syn);
                }
                if (counter % 1000 == 0) { // 100 at a time
                    session.flushToIndexes();
                    session.clear();
                }
            }
        }
        finally {
            session.clear();
            session.getSearchFactory().optimize();
        }
    }


    /**
     * TODO Method isWord ...
     *
     * @param s of type String
     * @return boolean
     */
    private static boolean isWord(String s) {
        int len = s.length();
        for (int i = 0; i < len; i++) {
            if (!Character.isLetter(s.charAt(i))) {
                return false;
            }
        }
        return true;
    }

    /**
     * TODO Method index ...
     *
     * @param word2Groups of type Map<String, List<String>>
     * @param group2Words of type Map<String, List<String>>
     * @param word2GroupsKey of type String
     * @param syn of type Synonym
     * @return int
     */
    private int index(Map<String, List<String>> word2Groups,
                      Map<String, List<String>> group2Words,
                      String word2GroupsKey,
                      Synonym syn) {
        List keys = word2Groups.get(word2GroupsKey); // get list of key#'s
        Iterator iter = keys.iterator();
        Set alreadyPresent = new TreeSet();

        while (iter.hasNext()) { // for each key# pass fill up 'alreadyPresent' with all words
            alreadyPresent.addAll(group2Words.get(iter.next())); // get list of words
        }
        int num = 0;
        alreadyPresent.remove(word2GroupsKey); // word is it's own synonym
        Iterator it = alreadyPresent.iterator();
        while (it.hasNext()) {
            String cur = (String) it.next();
            if (cur.startsWith("flick knife")) {
                System.out.println("");
            }
            if (!isWord(cur)) { // don't store things with spaces or non-alphas
                continue;
            }
            num++;
            if (syn.getSyn() != null) {
                syn.setSyn(syn.getSyn() + " " + cur);
            } else {
                syn.setSyn(cur);
            }
        }
        return num;
    }

    /**
     * Method setHibernateSearchBaseDir sets the hibernateSearchBaseDir of this SynonymHelper object
     *
     * @param hibernateSearchBaseDir of type String
     */
    public void setHibernateSearchBaseDir(String hibernateSearchBaseDir) {
        this.hibernateSearchBaseDir = hibernateSearchBaseDir;
    }
}
